package com.derbysoft.dswitch.adapter.ihg.dswitch3.service.impl;

import com.derbysoft.dswitch.adapter.ihg.common.domain.ReservationItem;
import com.derbysoft.dswitch.adapter.ihg.common.domain.ReservationModifyItem;
import com.derbysoft.dswitch.adapter.ihg.common.domain.Status;
import com.derbysoft.dswitch.adapter.ihg.common.log.LogBody;
import com.derbysoft.dswitch.adapter.ihg.common.log.PerformanceLogger;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ResModifyItemRepository;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ReservationRepository;
import com.derbysoft.dswitch.adapter.ihg.common.utils.DateTimeUtils;
import com.derbysoft.dswitch.adapter.ihg.common.utils.EnvironmentUtils;
import com.derbysoft.dswitch.adapter.ihg.common.utils.ErrorMessageUtils;
import com.derbysoft.dswitch.adapter.ihg.dswitch2.service.impl.ReservationRateHelper;
import com.derbysoft.dswitch.adapter.ihg.dswitch3.HotelProviderService;
import com.derbysoft.dswitch.adapter.ihg.dswitch3.translate.Translator;
import com.derbysoft.dswitch.adapter.ihg.dto.*;
import com.derbysoft.dswitch.adapter.ihg.exception.ConfirmNumberNotFoundException;
import com.derbysoft.dswitch.adapter.ihg.exception.InvokeProviderSideException;
import com.derbysoft.dswitch.adapter.ihg.webservice.handler.WebServiceHandler;
import com.derbysoft.dswitch.adapter.ihg.webservice.impl.AccountProvider;
import com.derbysoft.dswitch.dto.common.KeyValue;
import com.derbysoft.dswitch.dto.hotel.avail.AvailRoomStayDTO;
import com.derbysoft.dswitch.dto.hotel.avail.HotelAvailRoomStayDTO;
import com.derbysoft.dswitch.dto.hotel.common.CustomerDTO;
import com.derbysoft.dswitch.dto.hotel.common.StayDateRangeDTO;
import com.derbysoft.dswitch.dto.hotel.common.TransactionActionType;
import com.derbysoft.dswitch.dto.hotel.reservation.ReservationRoomStayDTO;
import com.derbysoft.dswitch.remote.hotel.dto.ErrorDTO;
import com.derbysoft.dswitch.remote.hotel.dto.HotelAvailResponse;
import com.derbysoft.dswitch.remote.hotel.dto.HotelReservationRequest;
import com.derbysoft.dswitch.remote.hotel.dto.HotelReservationResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

/**
 * Created by: jason
 * Date: 2012-05-17
 */
@Service("hotelReservationService")
public class HotelReservationServiceImpl extends BaseHotelService
        implements HotelProviderService<HotelReservationRequest, HotelReservationResponse> {

    private static final String CONFIRM_NUMBER_TYPE = "14";

    @Autowired
    private Translator<HotelReservationRequest, OTAHotelResRQ, Void> hotelCommitRQTranslator;

    @Autowired
    private Translator<HotelAvailResponse, HotelReservationResponse, HotelReservationRequest> hotelCommitRSTranslator;

    @Autowired
    @Qualifier("bookWebServiceHandler")
    private WebServiceHandler<OTAHotelResRQ, OTAHotelResRS> hotelCommitWebService;

    @Autowired
    @Qualifier("availWebServiceHandler")
    private WebServiceHandler<OTAHotelAvailRQ, OTAHotelAvailRS> hotelAvailWebService;

    @Autowired
    private Translator<HotelReservationRequest, OTAHotelAvailRQ, Void> hotelPreviewRQTranslatorV2;

    @Autowired
    @Qualifier("hotelAvailRSTranslatorV2")
    private Translator<OTAHotelAvailRS, HotelAvailResponse, HotelReservationRequest> hotelAvailRSTranslatorV2;

    @Autowired
    private ReservationRepository reservationRepository;

    @Autowired
    private ResModifyItemRepository resModifyItemRepository;

    @Override
    public HotelReservationResponse invoke(HotelReservationRequest reservationRequest) {
        StopWatch totalStopWatch = new StopWatch();
        totalStopWatch.start();
        try {
            OTAHotelAvailRQ otaHotelAvailRQ = hotelPreviewRQTranslatorV2.translate(reservationRequest, null);
            OTAHotelAvailRS otaHotelAvailRS = hotelAvailWebService.invoke(otaHotelAvailRQ, true,
                    AccountProvider.of(EnvironmentUtils.getGlobalIhgAvailabilityUrl(), loadAuthorization(reservationRequest.getHeader().getSource())));
            StopWatch availabilityStopWatch = new StopWatch();
            availabilityStopWatch.start();
            HotelAvailResponse hotelAvailResponse = hotelAvailRSTranslatorV2.translate(otaHotelAvailRS, reservationRequest);
            availabilityStopWatch.stop();
            String availabilityElapsedTime = String.valueOf(availabilityStopWatch.getTotalTimeMillis());
            if (hotelAvailResponse.getError() != null || findAvailRoomStay(hotelAvailResponse, reservationRequest) == null) {
                return noAllotmentResponse(otaHotelAvailRS, reservationRequest, availabilityElapsedTime);
            }

            OTAHotelResRQ otaHotelResRQ = hotelCommitRQTranslator.translate(reservationRequest, null);

            StopWatch reservationStopWatch = new StopWatch();
            reservationStopWatch.start();
            OTAHotelResRS otaHotelResRS = hotelCommitWebService.invoke(otaHotelResRQ, true,
                    AccountProvider.of(EnvironmentUtils.getIhgReservationUrl(), loadAuthorization(reservationRequest.getHeader().getSource())));
            reservationStopWatch.stop();
            String reservationElapsedTime = String.valueOf(reservationStopWatch.getTotalTimeMillis());

            saveOrUpdateReservation(reservationRequest);

            if (otaHotelResRS.getErrors() != null) {
                updateStatus(reservationRequest.getHotelReservationRQ().getResActionType(),
                        reservationRequest.getHotelReservationRQ().getErsp(), false);
                return createFailedResponse(reservationRequest, otaHotelResRS, reservationElapsedTime);
            }

            String ersp = reservationRequest.getHotelReservationRQ().getErsp();

            saveOrUpdateReservationItem(reservationRequest, ersp, findConfirmNumber(ersp, otaHotelResRS));

            HotelReservationResponse reservationResponse = hotelCommitRSTranslator.translate(hotelAvailResponse, reservationRequest);
            reservationResponse.getElapsedTimes().add(new KeyValue(ElapsedTimeType.RESERVATION.name(), reservationElapsedTime));
            reservationResponse.getHotelReservationRS().getHotelReservation().getRoomStaysList().get(0).setHotelReservationNo(findConfirmNumber(ersp, otaHotelResRS));

            String ratePlanCode = reservationRequest.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0).getRatePlanCode();
            String roomTypeCode = reservationRequest.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0).getRoomTypeCode();
            if (!ReservationRateHelper.check(otaHotelAvailRS, otaHotelResRS, ratePlanCode, roomTypeCode)) {
                OTAHotelAvailRS otaHotelAvailRS2 = hotelAvailWebService.invoke(otaHotelAvailRQ, true,
                        AccountProvider.of(EnvironmentUtils.getGlobalIhgAvailabilityUrl(),
                                loadAuthorization(reservationRequest.getHeader().getSource())));
                if (otaHotelAvailRS2 != null && otaHotelAvailRS2.getSuccess() != null) {
                    ReservationRateHelper.check(otaHotelAvailRS, otaHotelResRS, ratePlanCode, roomTypeCode);
                }
            }
            return reservationResponse;
        } catch (Exception ex) {
            if (totalStopWatch.isRunning()) {
                totalStopWatch.stop();
            }
            String reservationElapsedTime = String.valueOf(totalStopWatch.getTotalTimeMillis());
            HotelReservationResponse failedResponse = failedResponse(reservationRequest, ex);
            failedResponse.getElapsedTimes().add(new KeyValue(ElapsedTimeType.RESERVATION.name(), reservationElapsedTime));
            PerformanceLogger.append(LogBody.ERROR_CODE, failedResponse.getError().getCode());
            return failedResponse;
        }
    }

    private String findConfirmNumber(String ersp, OTAHotelResRS otaHotelResRS) {
        UniqueId uniqueId = otaHotelResRS.getHotelReservations().getHotelReservation().get(0).getUniqueID();
        if (uniqueId.getType().equals(CONFIRM_NUMBER_TYPE)) {
            return uniqueId.getID();
        }
        throw new ConfirmNumberNotFoundException(ersp);
    }

    private void saveOrUpdateReservationItem(HotelReservationRequest reservationRequest, String ersp, String confirmNumber) {
        ReservationRoomStayDTO roomStayDTO = reservationRequest.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0);

        if (reservationRequest.getHotelReservationRQ().getResActionType() == TransactionActionType.BOOK) {
            reservationRepository.addReservationItem(createReservationItem(roomStayDTO, ersp, confirmNumber));
        } else {
            resModifyItemRepository.save(createModifyItem(roomStayDTO, ersp, confirmNumber));
        }
    }

    private ReservationItem createReservationItem(ReservationRoomStayDTO roomStayDTO, String ersp, String confirmNumber) {
        StayDateRangeDTO stayDateRange = roomStayDTO.getStayDateRange();
        CustomerDTO customerDTO = roomStayDTO.getGuestsList().get(0);
        String givenName = customerDTO.getPersonName().getGivenName();
        String surname = customerDTO.getPersonName().getSurname();
        String ratePlanCode = roomStayDTO.getRatePlanCode();
        String roomTypeCode = roomStayDTO.getRoomTypeCode();
        int adults = roomStayDTO.getAdultCount();
        int children = roomStayDTO.getChildCount();
        int numberOfUnit = roomStayDTO.getNumberOfUnits();

        ReservationItem reservationItem = new ReservationItem();
        reservationItem.setErsp(ersp);
        reservationItem.setGivenName(givenName);
        reservationItem.setSurename(surname);
        reservationItem.setStart(stayDateRange.getCheckin());
        reservationItem.setDuration(DateTimeUtils.getLengthOfStay(stayDateRange.getCheckin(), stayDateRange.getCheckout()));
        reservationItem.setRatePlanCode(ratePlanCode);
        reservationItem.setRoomTypeCode(roomTypeCode);
        reservationItem.setConfirmNumber(confirmNumber);
        reservationItem.setAdults(adults);
        reservationItem.setChildren(children);
        reservationItem.setNumberOfUnit(numberOfUnit);
        return reservationItem;
    }

    private ReservationModifyItem createModifyItem(ReservationRoomStayDTO roomStayDTO, String ersp, String confirmNumber) {
        StayDateRangeDTO stayDateRange = roomStayDTO.getStayDateRange();
        CustomerDTO customerDTO = roomStayDTO.getGuestsList().get(0);
        String givenName = customerDTO.getPersonName().getGivenName();
        String surname = customerDTO.getPersonName().getSurname();
        String ratePlanCode = roomStayDTO.getRatePlanCode();
        String roomTypeCode = roomStayDTO.getRoomTypeCode();
        int adults = roomStayDTO.getAdultCount();
        int children = roomStayDTO.getChildCount();
        int numberOfUnit = roomStayDTO.getNumberOfUnits();

        ReservationModifyItem reservationModifyItem = new ReservationModifyItem();
        reservationModifyItem.setErsp(ersp);
        reservationModifyItem.setGivenName(givenName);
        reservationModifyItem.setSurename(surname);
        reservationModifyItem.setStart(stayDateRange.getCheckin());
        reservationModifyItem.setDuration(DateTimeUtils.getLengthOfStay(stayDateRange.getCheckin(), stayDateRange.getCheckout()));
        reservationModifyItem.setRatePlanCode(ratePlanCode);
        reservationModifyItem.setRoomTypeCode(roomTypeCode);
        reservationModifyItem.setConfirmNumber(confirmNumber);
        reservationModifyItem.setAdults(adults);
        reservationModifyItem.setChildren(children);
        reservationModifyItem.setNumberOfUnit(numberOfUnit);
        return reservationModifyItem;
    }

    private void updateStatus(TransactionActionType actionType, String ersp, boolean success) {
        if (actionType == TransactionActionType.BOOK) {
            reservationRepository.updateStatus(ersp, success ? Status.CONFIRMED : Status.BOOK_FAILED);
        } else if (actionType == TransactionActionType.MODIFY) {
            reservationRepository.updateStatus(ersp, success ? Status.MODIFIED : Status.MODIFY_FAILED);
        }
    }

    private void saveOrUpdateReservation(HotelReservationRequest reservationRequest) {
        if (reservationRequest.getHotelReservationRQ().getResActionType() == TransactionActionType.MODIFY) {
            reservationRepository.updateStatus(reservationRequest.getHotelReservationRQ().getErsp(), Status.MODIFIED);
        } else {
            reservationRepository.create(reservationRequest.getHotelReservationRQ().getErsp(),
                    reservationRequest.getHotelReservationRQ().getHotelReservation().getHotelCode(), Status.CONFIRMED.name());
        }
    }

    private HotelReservationResponse createFailedResponse(HotelReservationRequest reservationRequest, OTAHotelResRS otaHotelResRS, String webserviceTime) {
        HotelReservationResponse reservationResponse = new HotelReservationResponse();
        reservationResponse.setTaskId(reservationRequest.getHeader().getTaskId());
        reservationResponse.setError(createError(otaHotelResRS.getErrors()));
        reservationResponse.getElapsedTimes().add(new KeyValue(ElapsedTimeType.RESERVATION.name(), webserviceTime));
        return reservationResponse;
    }

    private HotelReservationResponse failedResponse(HotelReservationRequest request, Exception ex) {
        HotelReservationResponse response = new HotelReservationResponse();
        response.setTaskId(request.getHeader().getTaskId());
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setCode(SYSTEM);
        errorDTO.setSource(ex instanceof InvokeProviderSideException ? provider : adapter);
        errorDTO.setMessage(ErrorMessageUtils.buildErrorMessage(ex));
        response.setError(errorDTO);
        return response;
    }

    private AvailRoomStayDTO findAvailRoomStay(HotelAvailResponse availResponse, HotelReservationRequest reservationRequest) {
        ReservationRoomStayDTO reservationRoomStayDTO = reservationRequest.getHotelReservationRQ()
                .getHotelReservation().getRoomStaysList().get(0);
        HotelAvailRoomStayDTO availRoomStayDTO = availResponse.getHotelAvailRS().getHotelAvailRoomStaysList().get(0);
        for (AvailRoomStayDTO roomStayDTO : availRoomStayDTO.getRoomStaysList()) {
            if (roomStayDTO.getRatePlan().getCode().equals(reservationRoomStayDTO.getRatePlanCode())
                    || roomStayDTO.getRoomType().getCode().equals(reservationRoomStayDTO.getRoomTypeCode())) {
                return roomStayDTO;
            }
        }
        return null;
    }
}
